<template>
    <div id="app">
        <div id="info">
            <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - FBXLoader<br />
            Character and animation from <a href="https://www.mixamo.com/" target="_blank" rel="noopener">Mixamo</a>
        </div>
        <div ref="container" class="renderer-container"></div>
    </div>
</template>

<script>
import { onMounted, onBeforeUnmount, ref, shallowRef, markRaw, nextTick } from 'vue';
import * as THREE from 'three';
import Stats from 'three/examples/jsm/libs/stats.module.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';

export default {
    name: 'FBXLoaderComponent',
    setup() {
        const container = ref(null);
        const camera = shallowRef(null);
        // let scene = shallowRef(null);
        const renderer = shallowRef(null);
        const stats = ref(null);
        const object = shallowRef(null);
        const loader = ref(null);
        const mixer = shallowRef(null);
        const clock = new THREE.Clock();
        const params = ref({
            asset: 'Samba Dancing'
        });
        const assets = ref([
            'Samba Dancing',
            'morph_test'
        ]);
        const controls = ref(null);
        const gui = ref(null);
        const guiMorphsFolder = ref(null);


        // instance = getCurrentInstance();
        // const { proxy } = instance;
        // proxy.$scene = new THREE.Scene();
        // scene.value = proxy.$scene;

        let scene = markRaw(new THREE.Scene());

        const init = () => {
            camera.value = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
            camera.value.position.set(100, 200, 300);

            // instance = getCurrentInstance();
            // const { proxy } = instance;
            // proxy.$scene = new THREE.Scene();
            // // scene.value = new THREE.Scene();
            // scene.value = proxy.$scene;
            scene.background = new THREE.Color(0xa0a0a0);
            scene.fog = new THREE.Fog(0xa0a0a0, 200, 1000);

            const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 5);
            hemiLight.position.set(0, 200, 0);
            scene.add(hemiLight);

            const dirLight = new THREE.DirectionalLight(0xffffff, 5);
            dirLight.position.set(0, 100, 100);
            dirLight.castShadow = true;
            dirLight.shadow.camera.top = 180;
            dirLight.shadow.camera.bottom = -100;
            dirLight.shadow.camera.left = -120;
            dirLight.shadow.camera.right = 120;
            scene.add(dirLight);

            const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2000, 2000), new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false }));
            mesh.rotation.x = -Math.PI / 2;
            mesh.receiveShadow = true;
            scene.add(mesh);

            const grid = new THREE.GridHelper(2000, 20, 0x000000, 0x000000);
            grid.material.opacity = 0.2;
            grid.material.transparent = true;
            scene.add(grid);

            loader.value = new FBXLoader();
            loadAsset(params.value.asset);

            renderer.value = new THREE.WebGLRenderer({ antialias: true });
            renderer.value.setPixelRatio(window.devicePixelRatio);
            renderer.value.setSize(window.innerWidth, window.innerHeight);
            renderer.value.setAnimationLoop(animate);
            renderer.value.shadowMap.enabled = true;
            container.value.appendChild(renderer.value.domElement);

            controls.value = new OrbitControls(camera.value, renderer.value.domElement);
            controls.value.target.set(0, 50, 0);
            controls.value.update();

            window.addEventListener('resize', onWindowResize);

            stats.value = new Stats();
            container.value.appendChild(stats.value.dom);

            gui.value = new GUI();
            gui.value.add(params.value, 'asset', assets.value).onChange(value => {
                loadAsset(value);
            });

            guiMorphsFolder.value = gui.value.addFolder('Morphs').hide();
        };

        const loadAsset = (asset) => {
            const textureLoader = new THREE.TextureLoader();
            const textures = {};

            ['diffuse', 'emissive', 'height', 'glossiness', 'normal', 'specular'].forEach(mapType => {
                textures[mapType] = textureLoader.load(`textures/${asset}/${mapType}.jpg`);
            });

            loader.value.load(`models/fbx/${asset}.fbx`, group => {
                if (object.value) {
                    console.log('Object modelViewMatrix:', object.value.modelViewMatrix);
                    object.value.traverse(child => {
                        if (child.material) {
                            const materials = Array.isArray(child.material) ? child.material : [child.material];
                            materials.forEach(material => {
                                if (material.map) material.map.dispose();
                                material.dispose();
                            });
                        }
                        if (child.geometry) child.geometry.dispose();
                    });
                    scene.remove(object.value);
                }

                object.value = group;

                if (object.value.animations && object.value.animations.length) {
                    mixer.value = new THREE.AnimationMixer(object.value);
                    const action = mixer.value.clipAction(object.value.animations[0]);
                    action.play();
                } else {
                    mixer.value = null;
                }

                guiMorphsFolder.value.children.forEach(child => child.destroy());
                guiMorphsFolder.value.hide();

                object.value.traverse(child => {
                    if (child.isMesh) {
                        child.castShadow = true;
                        child.receiveShadow = true;

                        if (child.material) {
                            child.material.map = textures.diffuse;
                            child.material.emissiveMap = textures.emissive;
                            child.material.bumpMap = textures.height;
                            child.material.roughnessMap = textures.glossiness;
                            child.material.normalMap = textures.normal;
                            child.material.specularMap = textures.specular;
                            child.material.needsUpdate = true;
                        }

                        if (child.morphTargetDictionary) {
                            guiMorphsFolder.value.show();
                            const meshFolder = guiMorphsFolder.value.addFolder(child.name || child.uuid);
                            Object.keys(child.morphTargetDictionary).forEach(key => {
                                meshFolder.add(child.morphTargetInfluences, child.morphTargetDictionary[key], 0, 1, 0.01);
                            });
                        }
                    }
                });

                scene.add(object.value);
            });
        };

        const onWindowResize = () => {
            camera.value.aspect = window.innerWidth / window.innerHeight;
            camera.value.updateProjectionMatrix();
            renderer.value.setSize(window.innerWidth, window.innerHeight);
        };

        const animate = () => {
            const delta = clock.getDelta();
            if (mixer.value) mixer.value.update(delta);

            const rawScene = scene.value || scene;
            const rawCamera = camera.value;
            const rawRenderer = renderer.value;

            if (rawScene && rawCamera && rawRenderer) {
                try {
                    nextTick(() => {
                        rawRenderer.render(rawScene, rawCamera);
                    });
                } catch (error) {
                    console.error('Render error:', error);
                }
            }

            stats.value.update();
        };

        onMounted(() => {
            init();
        });

        onBeforeUnmount(() => {
            if (controls.value) {
                controls.value.dispose();
            }
            renderer.value.setAnimationLoop(null);
            if (gui.value) {
                gui.value.destroy();
            }
        });

        return {
            container
        };
    }
};
</script>

<style>
#app {
    font-family: Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
}

.renderer-container {
    position: fixed;
    left: 0;
    top: 0;
    width: 100vw;
    height: 100vh;
    overflow: hidden;
}

#info {
    position: absolute;
    top: 10px;
    width: 100%;
    text-align: center;
    z-index: 100;
    display: block;
    color: white;
}
</style>
